import os
import pathlib
import random
import subprocess
import shutil
import stat

def raw2json( cmd_raw ):
    return os.popen('xrntranslate-cli --cmd2json -json /dev/stdout ' + cmd_raw ).read()

def json2raw( jcmd_str, cache_dir ):

    # create cache directory
    cache_dir = cache_dir + "/" + os.path.basename(__file__).replace(".py","_json2raw_") + str(random.randint(0,10000))
    if not os.path.isdir( cache_dir ) :
        os.makedirs(cache_dir, exist_ok=True)
        #os.mkdir(cache_dir)

    # write string into a json file
    tmp_file_path = cache_dir + "/json2raw.json"
    with open( tmp_file_path , "w") as file :
        file.write(jcmd_str)

    # get the raw command
    retstr = os.popen('xrntranslate-cli --json2cmd -json ' + tmp_file_path ).read()

    # clean and exit
    #os.system("rm -rf " + cache_dir)
    return retstr

def check_inputs( jcmd_str, cache_dir ):

    # create cache directory
    cache_dir = cache_dir + "/" + os.path.basename(__file__).replace(".py","_check_inputs_") + str(random.randint(0,10000))
    if not os.path.isdir( cache_dir ) :
        os.makedirs(cache_dir, exist_ok=True)
    print(cache_dir)
    print(os.path.abspath(cache_dir))
    cache_dir = os.path.abspath(cache_dir)
    # write string into a json file
    command_file_path = cache_dir + "/check_inputs_command.json"
    print(jcmd_str)
    print(command_file_path)
    try: 
        with open( command_file_path , "w") as file :
            
            file.write(jcmd_str)
    except Exception as e:
        print(e)


    cmd = "xrnarchive-cli --check-inputs -json-command " + command_file_path  + " -json-log /dev/stdout "

    # run command
    print(cmd)
    retstr = os.popen(cmd).read()
    print(retstr)

    # check errors
    error = 0
    retjson = json.loads(retstr)
    for file in retjson["files"] :
        if (file["error_message"] != "" ) :
             error = 1

    # clean and exit
    #os.system("rm -rf " + cache_dir)
    return error, retstr

def check_outputs( jcmd_str, cache_dir ):

    # create cache directory
    cache_dir = cache_dir + "/" + os.path.basename(__file__).replace(".py","_check_outputs_") + str(random.randint(0,10000))
    if not os.path.isdir( cache_dir ) :
        os.makedirs(cache_dir, exist_ok=True)

    # write string into a json file
    command_file_path = cache_dir + "/check_outputs_command.json"
    with open( command_file_path , "w") as file :
        file.write(jcmd_str)

    cmd = "xrnarchive-cli --check-outputs -json-command " + command_file_path  + " -json-log /dev/stdout "

    # run command
    retstr = os.popen(cmd).read()

    # check errors
    error = 0
    retjson = json.loads(retstr)
    for file in retjson["files"] :
        if (file["error_message"] != "" ) :
             error = 1

    # clean and exit
    #os.system("rm -rf " + cache_dir)
    return error, retstr


def json2archive( jcmd_str, cache_dir ):

    # create cache directory
    cache_dir = cache_dir + "/" + os.path.basename(__file__).replace(".py","_json2archive_") + str(random.randint(0,10000))
    if not os.path.isdir( cache_dir ) :
        os.makedirs(cache_dir, exist_ok=True)
        #os.system("mkdir -p " + cache_dir )

    cmd_file = cache_dir + "/cmd.json"
    copy_and_archive_file = cache_dir + "/copy_and_archive.sh"
    extract_and_copy_file = cache_dir + "/exctract_and_copy_script.sh"

    with open ( cmd_file , "w" ) as file :
        print(cmd_file)
        file.write( jcmd_str )

    cmd = "xrnarchive-cli --generate-client-scripts -json-command " + cmd_file
    cmd = cmd + " -copy-inputs-and-archive-script " + copy_and_archive_file
    cmd = cmd + " -extract-outputs-and-copy-script " + extract_and_copy_file
    os.system(cmd)

    copy_and_archive_str = ""
    exctract_and_copy_script_str = ""

    with open ( copy_and_archive_file , "r" ) as file :
        copy_and_archive_str = file.read( )

    with open ( extract_and_copy_file , "r" ) as file :
        exctract_and_copy_script_str = file.read( )

    print(f"files: {extract_and_copy_file} ::: {copy_and_archive_file}")

    #os.system("rm -rf " + cache_dir) 
    #better to use shutil



    return copy_and_archive_str, exctract_and_copy_script_str , cmd_file

def json2archive_remote( jcmd_str, cache_dir ):

    # create cache directory
    cache_dir = cache_dir + "/" + os.path.basename(__file__).replace(".py","_json2archive_") + str(random.randint(0,10000))
    if not os.path.isdir( cache_dir ) :
        os.makedirs(cache_dir, exist_ok=True)
        #os.system("mkdir -p " + cache_dir )

    cmd_file = cache_dir + "/cmd.json"
    copy_and_archive_file = cache_dir + "/copy_and_archive.sh"
    extract_and_copy_file = cache_dir + "/exctract_and_copy_script.sh"
    def opener(path, flags):
        return os.open(path, flags, 0o700)

    with open ( cmd_file , "w",opener=opener ) as file :
        print(cmd_file)
        file.write( jcmd_str )

    cmd = "xrnarchive-cli --generate-client-scripts -json-command " + cmd_file
    cmd = cmd + " -copy-inputs-and-archive-script " + copy_and_archive_file
    cmd = cmd + " -extract-outputs-and-copy-script " + extract_and_copy_file
    os.system(cmd)

    

    # Add user execute permission
    current_permissions = os.stat(copy_and_archive_file).st_mode
    os.chmod(copy_and_archive_file, current_permissions | stat.S_IXUSR)

    current_permissions = os.stat(extract_and_copy_file).st_mode
    os.chmod(extract_and_copy_file, current_permissions | stat.S_IXUSR)
    

    # copy_and_archive_str = ""
    # exctract_and_copy_script_str = ""

    # with open ( copy_and_archive_file , "r" ) as file :
    #     copy_and_archive_str = file.read( )

    # with open ( extract_and_copy_file , "r" ) as file :
    #     exctract_and_copy_script_str = file.read( )

    #print(f"files: {extract_and_copy_file} ::: {copy_and_archive_file}")

    #os.system("rm -rf " + cache_dir) 
    #better to use shutil



    return copy_and_archive_file, extract_and_copy_file , cmd_file, cache_dir

def xrncmd_run ( cmd_str ) :
   

    global xrn_main_window_global
    slayout_local = xrn_main_window_global.jlayout_str

    cache_dir = get_from_layout( slayout_local, "general_cache_directory" )["fields"][2]["display"]
    local_remote = get_from_layout( slayout_local, "remote_local_execution_switch_id" )["fields"][2]["selection"] == "remote"
    remote_ip = get_from_layout( slayout_local, "remote_execution_ip_address" )["fields"][2]["display"]
    remote_tcp = get_from_layout( slayout_local, "remote_execution_tcp_address" )["fields"][2]["display"]

    check_inputs_local_str = ""
    check_output_local_str = ""
    check_inputs_remote_str = ""
    check_output_remote_str = ""

    if local_remote == 0 :
        #os.system( cmd_str )
        cmdoutput = ''
        proc = subprocess.run(cmd_str, shell=True, capture_output=True, env=os.environ, text=True)
        cmdoutput = f"Exit Code: {proc.returncode} output: {proc.stdout} "  

    else :
        print("remote")
        print(cache_dir)
        jcmd_str = raw2json( cmd_str )
        error_in, check_inputs_local_str = check_inputs( jcmd_str, cache_dir )
        print(f'error:{error_in}')
        if error_in == 0 :
            try:
                first_script, second_script , cmd_file, cache_dir1 = json2archive_remote(jcmd_str,cache_dir)
                print(first_script)
                os.system(first_script+" archive.tar "+ cache_dir1)
                cmd_str = "xrn-tcp-client --direction output "
                cmd_str += f"--ip {remote_ip} "
                cmd_str += f"--port {remote_tcp} "
                cmd_str += f"--archive {cache_dir1}/archive.tar"
                

                proc = subprocess.run(cmd_str, shell=True, capture_output=True, env=os.environ, text=True)
                
                if ( proc.returncode != 0):
                    check_inputs_remote_str = proc.stdout
                    print(check_inputs_remote_str)
                #pathlib.Path.unlink(f"{cache_dir1}/archive.tar") 



                cmd_str = "xrn-tcp-client --direction input "
                cmd_str += f"--ip {remote_ip} "
                cmd_str += f"--port {remote_tcp} "
                cmd_str += f"--cachedir {cache_dir1}"


                print(cmd_str)
                proc = subprocess.run(cmd_str, shell=True, capture_output=True, env=os.environ, text=True)
                
                if ( proc.returncode != 0):
                    check_inputs_remote_str = proc.stdout
                    print(check_inputs_remote_str)
                # send input archive
                # wait for output archive
                # fetch check_input_remote_str
                # fetch check_output_remote_str
                error_out, check_output_local_str = check_outputs( jcmd_str, cache_dir )
                if error_out == 0 :
                    os.system(second_script+f" {cache_dir1}/archive.tar "+ cache_dir1)
            except Exception as e:
                print(f"EX: {e}")
            pathlib.Path.unlink(cache_dir) 
    return check_inputs_local_str, check_output_local_str, check_inputs_remote_str, check_output_remote_str, cmdoutput


def load_xrn_json_header ( file_path, cache_dir, encoding_param_path, password_str, architecture ):

    # create cache directory
    cache_dir = cache_dir + "/" + os.path.basename(__file__).replace(".py","_load_xrn_json_header_") + str(random.randint(0,10000))
    os.makedirs(cache_dir, exist_ok=True)
    #os.system("mkdir -p " + cache_dir )

    if password_str == "" :
        pass_cmd = " -no-password "
    else :
        pass_cmd = " -password-str \"" + password_str + "\" "

    if encoding_param_path == "" :
        decoding_param_str = " -compiled-param " + pass_cmd
    else :
        decoding_param_str = " -encoding-param " + encoding_param_path + pass_cmd

    # create output file
    result_json = cache_dir + "/load_xrn_json_header_" + str(random.randint(0,10000)) + ".json"

    xrn_cmd_str = " \
        xrnlib-cli \
            --decoding-conf " + decoding_param_str + " \
            --permutation-conf -arch " + architecture + " \
            --logging-conf -force-write -lib-info-log " + result_json + " \
            --print-header -xrn-file " + file_path + " "

    input_local_check, output_local_check, input_remote_check, output_remote_check,cmd_output = xrncmd_run ( xrn_cmd_str )
    json_response = os.popen("cat " + result_json ).read()

    #os.system("rm -rf " + cache_dir)
    return json_response

def check_xrn_file_type( message_json_str, xrn_type ):

    if message_json_str == "" : 
        return 1

    message_json_obj = json.loads(message_json_str)
    type_in_header = [ "matrix", "matrix_compressed", "start", "point", \
                       "monovalent_key", "polyvalent_proof", "polyvalent_key", \
                       "monomial_key", "monomial_commitment", "monomial_response", \
                       "monomial_proof", "binomial_key", "binomial_commitment", \
                       "hash", "signature_sym", "signature_asy", "ring_signature", \
                       "certificate_sym", "certificate_asy",  \
                       "challenge", "asy_encryption", "sym_encryption", "solution" ] 

    type_in_message_type = [ "block", "encoding_parameters" ]

    if xrn_type in type_in_header :
        if message_json_obj["message_type"] == "data_structure" :
            if message_json_obj["header"]["type"] == xrn_type :
                 return 0

    elif xrn_type in type_in_message_type :
        if message_json_obj["message_type"] == xrn_type :
            return 0

    return 1


def check_matrx( file_path, cache_dir, encoding_param_path, password_str, architecture ):
    return check_xrn_file_type( load_xrn_json_header ( file_path, cache_dir, encoding_param_path, password_str, architecture ), "matrix" )

def check_matrx_compressed( file_path, cache_dir, encoding_param_path, password_str, architecture ):
    return check_xrn_file_type( load_xrn_json_header ( file_path, cache_dir, encoding_param_path, password_str, architecture ), "matrix_compressed" )

def check_start( file_path, cache_dir, encoding_param_path, password_str, architecture ):
    return check_xrn_file_type( load_xrn_json_header ( file_path, cache_dir, encoding_param_path, password_str, architecture ), "start" )

def check_point( file_path, cache_dir, encoding_param_path, password_str, architecture ):
    return check_xrn_file_type( load_xrn_json_header ( file_path, cache_dir, encoding_param_path, password_str, architecture ), "point" )

def check_monovalent_key( file_path, cache_dir, encoding_param_path, password_str, architecture ):
    return check_xrn_file_type( load_xrn_json_header ( file_path, cache_dir, encoding_param_path, password_str, architecture ), "monovalent_key" )

def check_polyvalent_proof( file_path, cache_dir, encoding_param_path, password_str, architecture ):
    return check_xrn_file_type( load_xrn_json_header ( file_path, cache_dir, encoding_param_path, password_str, architecture ), "polyvalent_proof" )

def check_polyvalent_key( file_path, cache_dir, encoding_param_path, password_str, architecture ):
    return check_xrn_file_type( load_xrn_json_header ( file_path, cache_dir, encoding_param_path, password_str, architecture ), "polyvalent_key" )

def check_monomial_key( file_path, cache_dir, encoding_param_path, password_str, architecture ):
    return check_xrn_file_type( load_xrn_json_header ( file_path, cache_dir, encoding_param_path, password_str, architecture ), "monomial_key" )

def check_monomial_commitment( file_path, cache_dir, encoding_param_path, password_str, architecture ):
    return check_xrn_file_type( load_xrn_json_header ( file_path, cache_dir, encoding_param_path, password_str, architecture ), "monomial_commitment" )

def check_monomial_response( file_path, cache_dir, encoding_param_path, password_str, architecture ):
    return check_xrn_file_type( load_xrn_json_header ( file_path, cache_dir, encoding_param_path, password_str, architecture ), "monomial_response" )

def check_monomial_proof( file_path, cache_dir, encoding_param_path, password_str, architecture ):
    return check_xrn_file_type( load_xrn_json_header ( file_path, cache_dir, encoding_param_path, password_str, architecture ), "monomial_proof" )

def check_binomial_key( file_path, cache_dir, encoding_param_path, password_str, architecture ):
    return check_xrn_file_type( load_xrn_json_header ( file_path, cache_dir, encoding_param_path, password_str, architecture ), "binomial_key" )

def check_binomial_commitment( file_path, cache_dir, encoding_param_path, password_str, architecture ):
    return check_xrn_file_type( load_xrn_json_header ( file_path, cache_dir, encoding_param_path, password_str, architecture ), "binomial_commitment" )

def check_hash( file_path, cache_dir, encoding_param_path, password_str, architecture ):
    return check_xrn_file_type( load_xrn_json_header ( file_path, cache_dir, encoding_param_path, password_str, architecture ), "hash" )

def check_signature_sym( file_path, cache_dir, encoding_param_path, password_str, architecture ):
    return check_xrn_file_type( load_xrn_json_header ( file_path, cache_dir, encoding_param_path, password_str, architecture ), "signature_sym" )

def check_signature_asy( file_path, cache_dir, encoding_param_path, password_str, architecture ):
    return check_xrn_file_type( load_xrn_json_header ( file_path, cache_dir, encoding_param_path, password_str, architecture ), "signature_asy" )

def check_ring_signature( file_path, cache_dir, encoding_param_path, password_str, architecture ):
    return check_xrn_file_type( load_xrn_json_header ( file_path, cache_dir, encoding_param_path, password_str, architecture ), "ring_signature" )

def check_certificate_sym( file_path, cache_dir, encoding_param_path, password_str, architecture ):
    return check_xrn_file_type( load_xrn_json_header ( file_path, cache_dir, encoding_param_path, password_str, architecture ), "certificate_sym" )

def check_certificate_asy( file_path, cache_dir, encoding_param_path, password_str, architecture ):
    return check_xrn_file_type( load_xrn_json_header ( file_path, cache_dir, encoding_param_path, password_str, architecture ), "certificate_asy" )

def check_sym_encryption( file_path, cache_dir, encoding_param_path, password_str, architecture ):
    return check_xrn_file_type( load_xrn_json_header ( file_path, cache_dir, encoding_param_path, password_str, architecture ), "sym_encryption" )

def check_asy_encryption( file_path, cache_dir, encoding_param_path, password_str, architecture ):
    return check_xrn_file_type( load_xrn_json_header ( file_path, cache_dir, encoding_param_path, password_str, architecture ), "asy_encryption" )

def check_challenge( file_path, cache_dir, encoding_param_path, password_str, architecture ):
    return check_xrn_file_type( load_xrn_json_header ( file_path, cache_dir, encoding_param_path, password_str, architecture ), "challenge" )

def check_solution( file_path, cache_dir, encoding_param_path, password_str, architecture ):
    return check_xrn_file_type( load_xrn_json_header ( file_path, cache_dir, encoding_param_path, password_str ), "solution" )

def check_block( file_path, cache_dir, password_str, architecture ):
    return check_xrn_file_type( load_xrn_json_header ( file_path, cache_dir, "", password_str ), "block" )

def check_encoding_param( file_path, cache_dir, password_str , architecture):
    return check_xrn_file_type( load_xrn_json_header ( file_path, cache_dir, "", password_str, architecture ) , "encoding_parameters" )


def check_password( pass_str ):

    if len(pass_str) < 10 :
        return "Error: password too short minimum 10 characters"

    if len(pass_str) > 254 :
        return "Error: password too long maximum 254 characters"

    chars = set('0123456789')
    if not any((c in chars) for c in pass_str):
        return "Error: password should contain one number"

    chars_str = '0123456789'
    chars = set( chars_str )
    if not any((c in chars) for c in pass_str):
        return "Error: password should contain one number " + chars_str

    chars_str = '!@#$%^&*()-_+=[]{}<>?/|\\`~;:'
    chars = set( chars_str )
    if not any((c in chars) for c in pass_str):
        return "Error: password should contain one special character " + chars_str

    contains_lowercase = 0
    contains_uppercase = 0
    for c in pass_str :
        if c.islower():
            contains_lowercase = 1
        if c.isupper():
            contains_uppercase = 1

    if contains_lowercase == 0 :
        return "Error: password should contain at least one lower case character"

    if contains_uppercase == 0 :
        return "Error: password should contain at least one upper case character"

    return ""


